package com.fuyun.rpc.client.proxy.retrystrategy;

import lombok.Data;
import lombok.extern.slf4j.Slf4j;

import java.io.IOException;
import java.net.ConnectException;
import java.net.SocketTimeoutException;
import java.util.Arrays;
import java.util.HashSet;
import java.util.Set;
import java.util.function.Supplier;

/**
 * @author: zrq1351
 * @date: 2025/2/16
 */
@Slf4j
@Data
public class RetryHandler implements RetryStrategy {
	private static final int MAX_RETRIES = 3;
	private static final long INITIAL_DELAY_MS = 1000;
	private static final double BACKOFF_FACTOR = 2.0;
	private static final double JITTER_FACTOR = 0.2;
	private Set<Class<? extends Exception>> retryableExceptions = new HashSet<>(
		Arrays.asList(SocketTimeoutException.class, ConnectException.class, IOException.class, RuntimeException.class));

	@Override
	public <T> T executeWithRetry(String serviceName, Supplier<T> supplier) {
		if (MAX_RETRIES <= 0) {
			throw new IllegalArgumentException("MAX_RETRIES 必须是正整数");
		}
		log.info("开始尝试请求 [{}] 服务", serviceName);

		int attempt = 0;
		while (attempt < MAX_RETRIES) {
			attempt++;
			try {
				return supplier.get();
			} catch (Exception e) {
				log.warn("尝试请求[{}]服务,第 {} 次失败。", serviceName, attempt, e);
				if (!shouldRetry(e)) {
					throw new RetryHandlerException(
						"尝试请求[" + serviceName + "]服务,第" + attempt + "次请求失败 " + attempt + " ", e);
				}

				long waitTime = calculateBackoffWithJitter(attempt);
				try {
					log.warn("尝试请求[{}]服务,第 {} 次失败。在 {}ms 后重试", serviceName, attempt, waitTime);
					Thread.sleep(waitTime);
				} catch (InterruptedException ie) {
					// 如果线程被中断，则抛出运行时异常
					Thread.currentThread().interrupt();
					throw new RetryHandlerException(
						"尝试请求[" + serviceName + "]服务,第" + attempt + "次请求失败 " + attempt + " ", ie);
				}
			}
		}

		throw new RetryHandlerException("尝试请求[" + serviceName + "]服务,第" + MAX_RETRIES + "失败");
	}

	/**
	 * 判断异常是否应该进行重试。
	 *
	 * @param e 需要判断的异常
	 * @return 如果异常是重试异常之一，则返回true，否则返回false
	 */
	private boolean shouldRetry(Exception e) {
		return retryableExceptions.stream().anyMatch(clazz -> clazz.isInstance(e));
	}

	/**
	 * 计算指数退避算法的退避时间，并添加随机抖动。
	 *
	 * @param attempt 当前尝试次数
	 * @return 计算后的退避时间（以毫秒为单位）
	 */
	private long calculateBackoffWithJitter(int attempt) {
		double exponentialBackoff = INITIAL_DELAY_MS * Math.pow(BACKOFF_FACTOR, attempt - 1);
		double jitter = exponentialBackoff * JITTER_FACTOR * Math.random();
		return (long)(exponentialBackoff + jitter);
	}
}